﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using MacomberMapSystem.Common.Serialization;
using MacomberMapSystem.Common.Database;
using System.Xml;
using System.Collections.Specialized;
using System.Data.Common;
using MacomberMapSystem.Common.Internals;
using System.Data.SQLite;
using MacomberMapSystem.Common.Integration;
using MacomberMapSystem.Common.Processing;

namespace MacomberMapSystem.Common.CIM
{
    /// <summary>
    /// This class holds information on a breaker-to-breaker definition
    /// </summary>
    public class CIM_Trace : CIM_FakeElement
    {
        #region Variable declarations
        /// <summary>The full list of elements associated with the breaker-to-breaker</summary>        
        [MM_Serializable_Target(MM_Serializable_Target.enumTargetType.FieldValue)]
        public override CIM_Element[] Elements
        {
            get
            {
                if (_Elements == null && rdfIDs is CIM_RdfID[] && rdfIDs.Length > 0)
                {
                    List<CIM_Element> OutElems = new List<CIM_Element>();
                    foreach (CIM_RdfID rdfID in rdfIDs)
                        OutElems.Add(CIM.FromRdfID(rdfID));
                    return OutElems.ToArray();
                }
                else
                    return _Elements;
            }
        }
        private CIM_Element[] _Elements, _Substations;

       
        /// <summary>The full list of elements associated with the breaker-to-breaker</summary>        
        [MM_Serializable_Target(MM_Serializable_Target.enumTargetType.FieldValue)]
        public CIM_Element[] Substations
        {
            get
            {
                if (_Substations == null && SubstationrdfIDs is CIM_RdfID[] && SubstationrdfIDs.Length > 0)
                {
                    List<CIM_Element> OutElems = new List<CIM_Element>();
                    foreach (CIM_RdfID rdfID in SubstationrdfIDs)
                        OutElems.Add(CIM.FromRdfID(rdfID));
                    return OutElems.ToArray();
                }
                else
                    return _Substations;
            }
            set { _Substations = value; }
        }

        /// <summary>Our collection of rdf:IDs implicated in the trace</summary>
        [MM_Serializable_Target(MM_Serializable_Target.enumTargetType.FieldValue)]
        public CIM_RdfID[] rdfIDs = null;

        /// <summary>Our collection of rdf:IDs between our contingency and the trace</summary>
        [MM_Serializable_Target(MM_Serializable_Target.enumTargetType.FieldValue)]
        public CIM_RdfID[] linkRdfIDs = null;

        /// <summary>Our collection of Substation rdf:IDs implicated in the trace</summary>
        [MM_Serializable_Target(MM_Serializable_Target.enumTargetType.FieldValue)]
        public CIM_RdfID[] SubstationrdfIDs = null;

        /// <summary>The full list of groups associated with the breaker-to-breaker</summary>
        [MM_Serializable_Target(MM_Serializable_Target.enumTargetType.FieldValue)]
        public CIM_Element[] Groups;

        /// <summary>The collection of RASs pointing to our element</summary>
        [MM_Serializable_Target(MM_Serializable_Target.enumTargetType.FieldValue)]
        public CIM_RdfID[] RASs = null;

        /// <summary>The description of the contingency</summary>
        [MM_Serializable_Target(MM_Serializable_Target.enumTargetType.FieldValue)]
        public String Description;

        /// <summary>Our collection of attributes defining our trace</summary>
        [MM_Serializable_Target(MM_Serializable_Target.enumTargetType.FieldValue)]
        public new MM_CIM_SortedCollection AttributeList = null;

        /// <summary>The names of our groups</summary>
        public String[] GroupNames;

        /// <summary>The full collection of group names</summary>
        public static SortedDictionary<String, int> FullGroupNames = new SortedDictionary<string, int>();

        /// <summary>
        /// Return the trace's descriptor as long name.
        /// </summary>
        public override string LongName
        {
            get { return Description; }
        }

        /// <summary>Whether the trace is defined as being radial</summary>
        [MM_Serializable_Target(MM_Serializable_Target.enumTargetType.FieldValue)]
        public int Radial = -1;

        /// <summary>Whether this trace has been matched</summary>
        public bool Matched = false;

        /// <summary>The collection of traces to which this list is connected</summary>
        public CIM_Trace[] ConnectedTraces
        {
            get
            {
                List<CIM_Trace> OutTraces = new List<CIM_Trace>();
                foreach (CIM_RdfID rdfID in this.rdfIDs)
                    foreach (CIM_Trace Trace in CIM.Traces.Values)
                        if (Trace != this && Array.IndexOf(Trace.rdfIDs, rdfID) != -1 && !OutTraces.Contains(Trace))
                            OutTraces.Add(Trace);
                return OutTraces.ToArray();
            }
        }

        /// <summary>Our list of lines that are at risk for being open-ended by this trace</summary>        
        [MM_Serializable_Target(MM_Serializable_Target.enumTargetType.FieldValue)]
        public CIM_Element[] OpenEndedLines
        {
            get { return _OpenEndedLines; }
            set { _OpenEndedLines = value; }
        }


        private CIM_Element[] _OpenEndedLines = new CIM_Element[0];
        #endregion

        #region Initialization
        /// <summary>
        /// Initialize a new trace from XML
        /// </summary>
        /// <param name="xElem"></param>
        /// <param name="CIM"></param>
        public CIM_Trace(XmlElement xElem, MM_Database_Model CIM)
            : base("cim:Contingency", new CIM_RdfID(xElem.Attributes["rdfID"].Value), CIM)
        {
            MM_Serializable.InitializeFromXml(this, xElem, CIM);
            if (rdfIDs == null && xElem.HasAttribute("TEIDs"))
            {
                List<CIM_RdfID> OutIDs = new List<CIM_RdfID>();
                foreach (String str in xElem.Attributes["TEIDs"].Value.Split(','))
                    using (SQLiteCommand sCmd = new SQLiteCommand("SELECT rdfID FROM CIM_TEIDs WHERE TEID=" + str, CIM.sConn))
                        OutIDs.Add(new CIM_RdfID((byte[])sCmd.ExecuteScalar()));
                this.rdfIDs = OutIDs.ToArray();
            }

            if (SubstationrdfIDs == null && xElem.HasAttribute("SubstationTEIDs"))
            {
                List<CIM_RdfID> OutIDs = new List<CIM_RdfID>();
                foreach (String str in xElem.Attributes["SubstationTEIDs"].Value.Split(','))
                    using (SQLiteCommand sCmd = new SQLiteCommand("SELECT rdfID FROM CIM_TEIDs WHERE TEID=" + str, CIM.sConn))
                        OutIDs.Add(new CIM_RdfID((byte[])sCmd.ExecuteScalar()));
                this.SubstationrdfIDs = OutIDs.ToArray();
            }

           GroupNames = xElem.Attributes["Groups"].Value.Split(',');
           foreach (String str in GroupNames)
               if (FullGroupNames.ContainsKey(str))
                   FullGroupNames[str]++;
               else
                   FullGroupNames.Add(str, 1);

 /*           List<CIM_Element> OutGroups = new List<CIM_Element>();
            foreach (CIM_Element CtgGroup in CIM.ElementsOfType("etx:ContingencyGroup"))
            {
                String CtgName = CtgGroup.Name;
                if (Array.IndexOf(GroupNames, CtgName) != -1)
                    OutGroups.Add(CtgGroup);              
            }
            this.Groups = OutGroups.ToArray();

*/
        }

        /// <summary>
        /// Initialize a new trace from an SQLite data reader
        /// </summary>
        /// <param name="sRd"></param>
        /// <param name="CIM"></param>
        public CIM_Trace(DbDataReader sRd, MM_Database_Model CIM)
            : base(sRd, CIM)
        { }

        /// <summary>
        /// Initialize a new breaker-to-breaker definition
        /// </summary>
        /// <param name="AssociatedModel"></param>
        /// <param name="SeedElement"></param>
        /// <param name="PlusOne"></param>
        /// <param name="StopNormalOpen"></param>
        /// <param name="Attribute"></param>  
        /// <param name="ElementsToParse"></param>  
        /// <param name="Name"></param>
        /// <param name="ID"></param>
        public CIM_Trace(Dictionary<CIM_Element, TraceState> ElementsToParse, String Name, MM_Database_Model AssociatedModel, CIM_Element SeedElement, bool PlusOne, bool StopNormalOpen, String Attribute, int ID)
            : base("cim:Trace", new CIM_RdfID(Guid.NewGuid().ToString()), AssociatedModel)
        {
            //Build our list of elements, and run our breaker-to-breaker trace
            this.ID = ID;
            List<CIM_Element> Elements = new List<CIM_Element>();
            List<CIM_Element> Substations = new List<CIM_Element>();
            Elements.Add(SeedElement);
            ParseLinks(Elements, SeedElement, PlusOne, StopNormalOpen, Attribute, 0, 1);


            this.Name = Name;

            //Determine our list of seeds, and remove ones that don't matter
            List<CIM_Element> Seeds = new List<CIM_Element>();
            TraceState FoundState;
            List<CIM_Element> Lines = new List<CIM_Element>();
            List<CIM_RdfID> rdfIDs = new List<CIM_RdfID>();

            foreach (CIM_Element Elem in Elements)
            {
                if (ElementsToParse.TryGetValue(Elem, out FoundState))
                    FoundState.AllocatedCtg = this;
                if (Elem.Substation != null && !Substations.Contains(Elem.Substation))
                    Substations.Add(Elem.Substation);
                if (Elem.TypeName == "cim:ACLineSegment")
                    Lines.Add(Elem);
                rdfIDs.Add(Elem.rdfID);
            }

            this.rdfIDs = rdfIDs.ToArray();
            _Substations = Substations.ToArray();
            _Elements = Elements.ToArray();
            ElementsToParse[SeedElement].AllocatedCtg = this;
        }

        /// <summary>
        /// Initialize a new trace against an existing definition
        /// </summary>
        /// <param name="ElementsToParse"></param>
        /// <param name="SeedElement"></param>
        /// <param name="SeedTypes"></param>
        /// <param name="Name"></param>
        /// <param name="ID"></param>
        public CIM_Trace(Dictionary<CIM_Element, TraceState> ElementsToParse, String Name, StringCollection SeedTypes, CIM_Element SeedElement, int ID)
            : base("cim:Trace", new CIM_RdfID(Guid.NewGuid().ToString()), SeedElement.CIM)
        {
            this.ID = ID;
            List<CIM_Trace> BaseTraces = new List<CIM_Trace>();
            TraceState FoundState;
            this.Name = Name;
            List<CIM_Element> Seeds = new List<CIM_Element>();
            List<CIM_Element> Elements = new List<CIM_Element>();
            List<CIM_Element> Substations = new List<CIM_Element>();
            List<CIM_Trace> UsedTraces = new List<CIM_Trace>();
            List<CIM_RdfID> rdfIDs = new List<CIM_RdfID>();
            foreach (CIM_Element Elem in SeedElement.Elements)
                if (SeedTypes.Contains(Elem.TypeName))
                    foreach (CIM_Element Node in Elem.Nodes)
                        if (ElementsToParse.TryGetValue(Node, out FoundState) && FoundState.AllocatedCtg != null && !UsedTraces.Contains(FoundState.AllocatedCtg))
                        {
                            UsedTraces.Add(FoundState.AllocatedCtg);
                            foreach (CIM_Element OutElem in FoundState.AllocatedCtg.Elements)
                                if (!Elements.Contains(OutElem))
                                {
                                    Elements.Add(OutElem);
                                    if (OutElem.Substation != null && !Substations.Contains(OutElem.Substation))
                                        Substations.Add(OutElem.Substation);
                                }
                        }
            _Substations = Substations.ToArray();
            _Elements = Elements.ToArray();
        }

        /// <summary>
        /// Search for open-ended line risks
        /// </summary>
        public CIM_Trace[] SearchForOpenEndedRisks()
        {
            //If we have one or more lines, look at potential open-ended line-line intersections
            List<CIM_Element> Lines = new List<CIM_Element>();
            List<CIM_Trace> Traces = new List<CIM_Trace>();
            foreach (CIM_Element Elem in Elements)
                if (Elem.TypeName == "cim:ACLineSegment")
                    Lines.Add(Elem);
            if (Lines.Count > 0)
            {
                StringBuilder sB = new StringBuilder();
                sB.AppendLine("Trace " + this.Name + " and its lines:");
                List<CIM_Element> OtherLines = new List<CIM_Element>();
                List<CIM_Element> AtRiskLines = new List<CIM_Element>();
                ParseLinks(OtherLines, Lines[0], false, true, "etx:PowerSystemResource.contingencyComponent", 0, 2);
                foreach (CIM_Element Elem in OtherLines)
                    if (Elem.TypeName == "cim:ACLineSegment")
                        if (Elements.Contains(Elem))
                            sB.AppendLine("\t" + Elem.LongName.Replace("\n", " ") + " ");
                        else
                            AtRiskLines.Add(Elem);
                this.OpenEndedLines = AtRiskLines.ToArray();

                if (AtRiskLines.Count > 0)
                {
                    sB.AppendLine("Are at risk for being open-ended by faults across lines:");
                    foreach (CIM_Element Elem in AtRiskLines)
                    {
                        sB.Append("\t" + Elem.LongName.Replace("\n", " ") + " ");
                        foreach (CIM_Trace Trace in CIM.TraceLinkages[Elem.rdfID])
                            if (!Trace.Name.StartsWith("D"))
                            {
                                sB.Append(Trace.Name + " ");
                                if (!Traces.Contains(Trace))
                                    Traces.Add(Trace);
                            }
                        sB.AppendLine();
                    }

                    Console.WriteLine(sB.ToString());
                }
            }
            return Traces.ToArray();
        }

        /// <summary>
        /// Perform breaker-to-breaker tracing until a target element type is hit. Added 11/29/11 for PUN/PV suppport.
        /// </summary>
        /// <param name="Elements"></param>
        /// <param name="SeedElement"></param>
        /// <param name="StopNormalOpenSwitchOrBreaker"></param>
        /// <param name="CtgAttribute"></param>
        /// <param name="Current"></param>
        public static void TraceUntilVector(List<CIM_Element> Elements, CIM_Element SeedElement, bool StopNormalOpenSwitchOrBreaker, string CtgAttribute, int Current)
        {
            if (Current == 500)
            {
                CIM_FakeElement Fake = new CIM_FakeElement("cim:ERROR", new CIM_RdfID("ERROR"), SeedElement.CIM);
                Fake.Name = "> 500 recursions deep.";
                return;
            }

            //Add our list of elements to be parsed
            List<CIM_Element> ElementsToParse = new List<CIM_Element>();

            foreach (CIM_Link term in SeedElement.InLinks)
                if (term.TypeName == "cim:Terminal" && !HasVector(term.Element.InLinks))
                {
                    foreach (CIM_Link Link in term.Element.OutLinks)
                        if (!Link.Element.Equals(SeedElement) && !ElementsToParse.Contains(Link.Element))
                            ElementsToParse.Add(Link.Element);
                }


            if (SeedElement.TypeName == "cim:TransformerWinding")
                foreach (CIM_Link WindingLink in SeedElement.OutLinks)
                    if (WindingLink.TypeName == "cim:PowerTransformer")
                        ElementsToParse.Add(WindingLink.Element);

            if (SeedElement.TypeName == "cim:PowerTransformer")
                foreach (CIM_Link WindingLink in SeedElement.InLinks)
                    if (WindingLink.TypeName == "cim:TransformerWinding")
                        ElementsToParse.Add(WindingLink.Element);

            foreach (CIM_Element Elem in ElementsToParse)
                if (!Elements.Contains(Elem))
                {
                    Elements.Add(Elem);
                    if (StopNormalOpenSwitchOrBreaker && (Elem.TypeName == "cim:Breaker" || Elem.TypeName == "cim:Disconnector") && bool.Parse(Elem["cim:Switch.normalOpen"]))
                    { }
                    else
                        TraceUntilVector(Elements, Elem, StopNormalOpenSwitchOrBreaker, CtgAttribute, Current + 1);
                }
        }

        /// <summary>
        /// Determine if a terminal has a pricing vector linked to it
        /// </summary>
        /// <param name="Links"></param>
        /// <returns></returns>
        private static bool HasVector(CIM_Link[] Links)
        {
            foreach (CIM_Link Link in Links)
                if (Link.TypeName == "etx:PricingVector")
                    return true;
            return false;
        }

        /// <summary>
        /// Parse links until we come to the end of our path
        /// </summary>
        /// <param name="Elements">The collection of elements that have been touched</param>
        /// <param name="SeedElement">The element from which the parsing should start</param>
        /// <param name="PlusOne">Whether to retreve the element on the other side of the breaker threshold</param>
        /// <param name="StopNormalOpen">Whether to stop on a normal-opened switch/breaker</param>
        /// <param name="CtgAttribute">The contingency attribute</param>
        /// <param name="BreakerCount">The number of breakers that have been hit already</param>
        /// <param name="BreakerThreshold">The threshold of breakers to be hit</param>
        private void ParseLinks(List<CIM_Element> Elements, CIM_Element SeedElement, bool PlusOne, bool StopNormalOpen, string CtgAttribute, int BreakerCount, int BreakerThreshold)
        {
            foreach (CIM_Element Elem in (SeedElement.TypeName == "cim:ConnectivityNode" ? SeedElement.Elements : SeedElement.Nodes))
                if (!Elements.Contains(Elem))
                {
                    Elements.Add(Elem);
                    //Stop parsing on a normally opened breaker/switch if requested
                    if (StopNormalOpen && (Elem.TypeName == "cim:Breaker" || Elem.TypeName == "cim:Disconnector") && bool.Parse(Elem["cim:Switch.normalOpen"]))
                    { }
                    //If we're at our breaker threshold, go no further
                    else if (BreakerCount == BreakerThreshold)
                    { }
                    else
                    {
                        //Determine if our current element counts as a breaker
                        bool IsBreaker = (Elem.TypeName == "cim:Breaker" && (String.IsNullOrEmpty(CtgAttribute) || String.IsNullOrEmpty(Elem[CtgAttribute]) || XmlConvert.ToBoolean(Elem[CtgAttribute]))) ||
                                    (Elem.TypeName == "cim:Disconnector" && !String.IsNullOrEmpty(CtgAttribute) && !String.IsNullOrEmpty(Elem[CtgAttribute]) && !XmlConvert.ToBoolean(Elem[CtgAttribute]));

                        //If we're at a breaker, flag accordingly
                        if (IsBreaker)
                            BreakerCount++;


                        //If we are a breaker at a plus one state, parse going forward only one element
                        if (IsBreaker && BreakerCount == BreakerThreshold && PlusOne)
                            ParseLinks(Elements, Elem, PlusOne, StopNormalOpen, CtgAttribute, BreakerCount, BreakerThreshold);

                        //Otherwise, if we're at a breaker but we're below our threshold, parse going forward
                        else if (IsBreaker && BreakerCount < BreakerThreshold)
                            ParseLinks(Elements, Elem, PlusOne, StopNormalOpen, CtgAttribute, BreakerCount, BreakerThreshold);

                        //Otherwise, parse forward
                        else if (!IsBreaker && (Elem.TypeName == "cim:ConnectivityNode" || BreakerCount < BreakerThreshold))
                            ParseLinks(Elements, Elem, PlusOne, StopNormalOpen, CtgAttribute, BreakerCount, BreakerThreshold);
                    }
                }
        }


        /// <summary>
        /// Go through the current contingnecy definition, and determine whether it could be classified as radial (1 or fewer non-radial breaker, no unit or transformer)
        /// </summary>
        /// <param name="RadialBreakers"></param>
        /// <param name="CtgAttribute"></param>
        /// <param name="Increment"></param>
        /// <param name="BreakerTally"></param>
        /// <returns></returns>
        public bool CheckRadial(Dictionary<CIM_Element, bool> RadialBreakers, String CtgAttribute, int Increment, Dictionary<CIM_Element, int> BreakerTally)
        {
            List<CIM_Element> Breaker = new List<CIM_Element>();
            foreach (CIM_Element Elem in Elements)
                if (Elem.TypeName == "cim:TransformerWinding" || Elem.TypeName == "cim:PowerTransformer" || Elem.TypeName == "cim:SynchronousMachine")
                    return false;
                else if (Elem.TypeName == "cim:Breaker" && !RadialBreakers.ContainsKey(Elem))
                    if (String.IsNullOrEmpty(CtgAttribute) || XmlConvert.ToBoolean(Elem[CtgAttribute]))
                        if (!BreakerTally.ContainsKey(Elem) || BreakerTally[Elem] > 1)
                            Breaker.Add(Elem);
            if (Breaker.Count < 2)
            {
                foreach (CIM_Element ThisBreaker in Breaker)
                    RadialBreakers.Add(ThisBreaker, true);
                Radial = Increment;
                return true;
            }
            else
                return false;
        }

        #endregion

        #region Trace state
        /// <summary>
        /// This class holds information on a trace state for an element
        /// </summary>
        public class TraceState
        {
            #region Variable declarations
            /// <summary>The trace associated with this element</summary>            
            public CIM_Trace AllocatedCtg = null;
            #endregion

            #region Initialization
            /// <summary>
            /// Initialize a new trace state
            /// </summary>
            public TraceState()
            {
            }

            /// <summary>
            /// Report an easy to read view of the contingency
            /// </summary>
            /// <returns></returns>
            public override string ToString()
            {
                if (AllocatedCtg == null)
                    return "()";
                else
                    return AllocatedCtg.Name;
            }
            #endregion
        }
        #endregion



        /// <summary>
        /// Write out the OS Related Equipment information for this trace
        /// </summary>
        /// <param name="xW"></param>
        /// <param name="TraceElem"></param>
        /// <param name="TypesToNotOutput"></param>
        public void WriteOSRelatedEquipment(XmlTextWriter xW, CIM_Element TraceElem, StringCollection TypesToNotOutput)
        {
            foreach (CIM_Element OtherElem in Elements)
                if (!OtherElem.Equals(TraceElem) && !TypesToNotOutput.Contains(OtherElem.ElemType))
                {
                    xW.WriteStartElement("NMMS_RELATED_EQUIPMENT");
                    xW.WriteElementString("EQUIPMENT_IDENT", TraceElem.rdfID.ToString());
                    xW.WriteElementString("RELATED_EQUIPMENT_IDENT", OtherElem.rdfID.ToString());
                    xW.WriteElementString("EFFECTIVE_DATE", DateTime.Now.ToString("yyyy:MM:dd"));
                    xW.WriteEndElement();
                }
        }








        /// <summary>
        /// Determine whether a trace can be matched up to a prior one.
        /// </summary>
        /// <param name="Traces"></param>
        /// <param name="ProcessLog"></param>
        /// <param name="ContingencySeeds"></param>
        /// <param name="SeedCount"></param>
        /// <param name="Description"></param>
        /// <param name="LineSubs"></param>
        /// <param name="TempName"></param>
        /// <param name="Units"></param>
        /// <param name="XFs"></param>
        /// <param name="Deltas"></param>
        /// <returns></returns>
        private bool MatchTrace(MM_Indexed_Dictionary<String, CIM_Trace> Traces, StringBuilder ProcessLog, Dictionary<CIM_RdfID, CIM_Element> ContingencySeeds, int SeedCount, List<CIM_Element> LineSubs, List<CIM_Element> Units, List<CIM_Element> XFs, out string TempName, out string Description, List<CIM_Processor_Delta> Deltas)
        {
            foreach (CIM_Trace Trace in Traces.Values)
                if (!Trace.Name.StartsWith("D"))
                {
                    //Count our matches of rdf:IDs between current and prior models
                    int Tally = 0, SeedTally = 0;
                    foreach (CIM_RdfID rdfID in rdfIDs)
                        if (Array.IndexOf(Trace.rdfIDs, rdfID) != -1)
                        {
                            if (ContingencySeeds.ContainsKey(rdfID))
                                SeedTally++;
                            Tally++;
                        }

                    //If our counts match up, handle accordingly.
                    if (Tally == rdfIDs.Length)
                    {
                        TempName = Trace.Name;
                        Description = Trace.Description;
                        this.rdfID = Trace.rdfID;
                        return ConfirmName(TempName, LineSubs, XFs, Units, ProcessLog);
                    }
                    else if (SeedCount > 0 && SeedTally == SeedCount)
                    {
                        TempName = Trace.Name;
                        Description = Trace.Description;
                        this.rdfID = Trace.rdfID;
                        foreach (CIM_RdfID rdfID in rdfIDs)
                            if (Array.IndexOf(Trace.rdfIDs, rdfID) == -1)
                            {
                                ProcessLog.AppendLine(String.Format("Contingency {0} added {1} (rdf:ID {2})", Trace.Name, CIM.FromRdfID(rdfID).ToString(), rdfID));
                                Deltas.Add(new CIM_Processor_Delta(typeof(CIM_BreakerToBreaker), CIM_Processor_Delta.enumDeltaType.Addition, this, rdfID));
                            }
                        foreach (CIM_RdfID rdfID in Trace.rdfIDs)
                            if (Array.IndexOf(rdfIDs, rdfID) == -1)
                            {
                                ProcessLog.AppendLine(String.Format("Contingency {0} removed rdf:ID {1}", Trace.Name, rdfID));
                                Deltas.Add(new CIM_Processor_Delta(typeof(CIM_BreakerToBreaker), CIM_Processor_Delta.enumDeltaType.Removal, this, rdfID));
                            }
                        return ConfirmName(TempName, LineSubs, XFs, Units, ProcessLog);

                    }
                }
            TempName = null;
            Description = null;
            return false;
        }

        /// <summary>
        /// Determine if a name is appropriate for our components
        /// </summary>
        /// <param name="ProposedName"></param>
        /// <param name="LineSubs"></param>
        /// <param name="XFs"></param>
        /// <param name="Units"></param>
        /// <param name="ProcessLog"></param>
        /// <returns></returns>
        private bool ConfirmName(string ProposedName, List<CIM_Element> LineSubs, List<CIM_Element> XFs, List<CIM_Element> Units, StringBuilder ProcessLog)
        {
            if (CIM.ReservedTraceNames.Contains(ProposedName))
                return false;
            else if (ProposedName.StartsWith("N_") && LineSubs.Count == 0 && Units.Count == 0 && XFs.Count == 0)
                return true;
            else if (ProposedName.StartsWith("S") && LineSubs.Count > 0)
                return true;
            else if (ProposedName.StartsWith("U") && Units.Count > 0)
                return true;
            else if (ProposedName.StartsWith("X") && XFs.Count > 0)
                return true;
            else
            {
                CIM.ReservedTraceNames.Add(ProposedName);
                return false;
            }
        }

        /// <summary>
        /// Write out our definition
        /// </summary>
        /// <param name="xW"></param>
        public void WriteDefinition(XmlTextWriter xW)
        {
            //First, write out our contingency definition
            xW.WriteStartElement("cim:Contingency");
            xW.WriteAttributeString("rdf:ID", this.rdfID.ToString());
            xW.WriteElementString("cim:IdentifiedObject.name", this.Name.ToUpper());
            xW.WriteElementString("cim:IdentifiedObject.aliasName", "");
            if (this.Description.Length > 28)
                xW.WriteElementString("cim:IdentifiedObject.description", this.Description.Substring(0, 28));
            else
                xW.WriteElementString("cim:IdentifiedObject.description", this.Description);
            xW.WriteElementString("cim:IdentifiedObject.localName", "");
            xW.WriteElementString("cim:IdentifiedObject.mRID", CIM.GetMRID().ToString());
            xW.WriteElementString("cim:IdentifiedObject.pathName", "");
            xW.WriteElementString("etx:Contingency.ActionType", "");
            xW.WriteStartElement("etx:Contingency.activeMode");
            xW.WriteAttributeString("rdf:resource", "http://www.ercot.com/CIM11R0/2008/2.0/extension#EnumContingencyActiveMode.default");
            xW.WriteEndElement();
            xW.WriteElementString("etx:Contingency.enableCascading", "false");
            xW.WriteElementString("etx:Contingency.enableRedefinition", "false");
            xW.WriteElementString("etx:Contingency.enableSPS", "true");
            xW.WriteElementString("etx:Contingency.generationLoss", "9999");
            xW.WriteStartElement("etx:Contingency.generatorParticipationFactor");
            xW.WriteAttributeString("rdf:resource", "http://www.ercot.com/CIM11R0/2008/2.0/extension#EnumGeneratorParticipationFactor.normal");
            xW.WriteEndElement();
            xW.WriteElementString("etx:Contingency.loadLoss", "9999");
            xW.WriteStartElement("etx:Contingency.processType");
            xW.WriteAttributeString("rdf:resource", "http://www.ercot.com/CIM11R0/2008/2.0/extension#EnumContingencyProcessType.useWide");
            xW.WriteEndElement();
            xW.WriteElementString("etx:Contingency.refactor", "false");

            xW.WriteEndElement();

            //Now, export the linkages to all elements
            for (int a = 0; a < Elements.Length; a++)
            {
                KeyValuePair<CIM_Element, CIM_RdfID> Elem = new KeyValuePair<CIM_Element, CIM_RdfID>(Elements[a], linkRdfIDs[a]);
                xW.WriteStartElement("etx:ContingencyElement");
                xW.WriteAttributeString("rdf:ID", Elem.Value.ToString().ToUpper());
                xW.WriteElementString("cim:IdentifiedObject.name", Elem.Key.Name);
                xW.WriteElementString("cim:IdentifiedObject.aliasName", "");
                xW.WriteElementString("cim:IdentifiedObject.description", Elem.Key["cim:IdentifiedObject.description"]);
                xW.WriteElementString("cim:IdentifiedObject.localName", "");
                xW.WriteElementString("cim:IdentifiedObject.mRID", CIM.GetMRID().ToString());
                xW.WriteElementString("cim:IdentifiedObject.pathName", "");
                xW.WriteElementString("etx:ContingencyElement.status", "true");

                xW.WriteStartElement("etx:ContingencyElement.MemberOf_Contingency");
                xW.WriteAttributeString("rdf:resource", "#" + this.rdfID.ToString());
                xW.WriteEndElement();

                xW.WriteStartElement("etx:ContingencyElement.Equipment");
                xW.WriteAttributeString("rdf:resource", "#" + Elem.Key.rdfID.ToString());
                xW.WriteEndElement();

                xW.WriteEndElement();
            }
        }

        #region Substation Comparisons
        /// <summary>
        /// This class allows for the comparison of two substations
        /// </summary>
        private class SubstationComparer : IComparer<CIM_Element>
        {
            /// <summary>
            /// Compare two substations
            /// </summary>
            /// <param name="x"></param>
            /// <param name="y"></param>
            /// <returns></returns>
            public int Compare(CIM_Element x, CIM_Element y)
            {
                int[] XFs = new int[] { 0, 0 };
                int[] Units = new int[] { 0, 0 };
                float[] Lines = new float[] { 0f, 0f };




                for (int a = 0; a < 2; a++)
                    foreach (CIM_Element Elem in (a == 0 ? x : y).Elements)
                    {
                        if (Elem.TypeName == "cim:ACLineSegment")
                        {
                            float MostConservative = float.NaN;
                            foreach (CIM_Element OwnerShareRating in Elem.Parse("etx:OwnerShareRating", null))
                                foreach (CIM_Element StaticRating in OwnerShareRating.Parse("etx:Rating", null))
                                    foreach (CIM_Element NormalRating in StaticRating.Parse("cim:AnalogLimit", null))
                                        if ((StaticRating.Name.Equals("staticRating", StringComparison.CurrentCultureIgnoreCase) || StaticRating.Name.Equals("Static", StringComparison.CurrentCultureIgnoreCase)) && (NormalRating.Name.Equals("normalRating", StringComparison.CurrentCultureIgnoreCase) || NormalRating.Name.Equals("NMRL", StringComparison.CurrentCultureIgnoreCase) || NormalRating.Name.Equals("NRML", StringComparison.CurrentCultureIgnoreCase)) && (float.IsNaN(MostConservative) || MostConservative > float.Parse(NormalRating["cim:AnalogLimit.value"])))
                                            MostConservative = float.Parse(NormalRating["cim:AnalogLimit.value"]);
                            if (float.IsNaN(MostConservative))
                                Console.WriteLine("Whoops!");

                            Lines[a] += MostConservative;
                        }
                        else if (Elem.TypeName == "cim:SynchronousMachine")
                            Units[a]++;
                        else if (Elem.TypeName == "cim:TransformerWinding")
                            XFs[a]++;
                    }


                int Comp;
                if ((Comp = Lines[0].CompareTo(Lines[1])) != 0)
                    return Comp;
                else if ((Comp = XFs[0].CompareTo(XFs[1])) != 0)
                    return Comp;
                else if ((Comp = Units[0].CompareTo(Units[1])) != 0)
                    return Comp;
                else
                    return x.Name.CompareTo(y.Name);
            }
        }

        #endregion

        /// <summary>
        /// Wire up a trace to its associated contingnecy elements
        /// </summary>
        public void WireTrace()
        {
            CIM_Element CIMVersion = CIM.FromRdfID(this.rdfID);
            if (CIMVersion != null)
            {
                List<CIM_RdfID> RASRdfIDs = new List<CIM_RdfID>();
                foreach (CIM_Link Link in CIMVersion.InLinks)
                    if (Link.TypeName == "etx:ContingencyElement")
                        RASRdfIDs.Add(Link.Element.rdfID);
                this.RASs = RASRdfIDs.ToArray();
                this.AttributeList = CIMVersion.AttributeList;
            }
        }

        /// <summary>
        /// Generate the name and description for our contingency definition
        /// </summary>
        /// <param name="Deltas"></param>
        /// <param name="ProcessLog"></param>
        /// <param name="ContingencySeeds"></param>
        public void GenerateNameAndDescription(Dictionary<CIM_RdfID, CIM_Element> ContingencySeeds, StringBuilder ProcessLog, List<CIM_Processor_Delta> Deltas)
        {
            //If our contingency name starts with D, just return that name.
            if (Name.StartsWith("D"))
                return;

            //Count our number of seeds
            int SeedCount = 0;
            foreach (CIM_RdfID rdfID in rdfIDs)
                if (ContingencySeeds.ContainsKey(rdfID))
                    SeedCount++;


            //First, check to see if we can find lines.
            List<CIM_Element> LineSubs = new List<CIM_Element>();
            List<MM_KVLevel> XFLevels = new List<MM_KVLevel>();
            List<CIM_Element> XFSubs = new List<CIM_Element>();
            List<CIM_Element> XFs = new List<CIM_Element>();
            List<CIM_Element> Units = new List<CIM_Element>();
            CIM_Element HighestVoltage = null;
            String TempName;
            foreach (CIM_Element Elem in Elements)
                if (ContingencySeeds.ContainsKey(Elem.rdfID))
                    if (Elem.TypeName == "cim:ACLineSegment")
                    {
                        foreach (CIM_Element LineSub in Elem.ACSegmentStations)
                            if (!LineSubs.Contains(LineSub))
                                LineSubs.Add(LineSub);
                        if (HighestVoltage == null || float.Parse(HighestVoltage.Name.Split(' ')[0]) < float.Parse(Elem.VoltageLevel.Name.Split(' ')[0]))
                            HighestVoltage = Elem.VoltageLevel;
                    }
                    else if (Elem.TypeName == "cim:PowerTransformer")
                    {
                        XFs.Add(Elem);
                        if (!XFSubs.Contains(Elem.Substation))
                            XFSubs.Add(Elem.Substation);
                        foreach (CIM_Element XFWinding in Elem.TransformerWinding)
                        {
                            MM_KVLevel KVLevel = Data_Manager.LocateKVLevel(XFWinding.VoltageLevel);
                            if (!XFLevels.Contains(KVLevel))
                                XFLevels.Add(KVLevel);
                        }
                    }
                    else if (Elem.TypeName == "cim:TransformerWinding")
                    {
                        CIM_Element XF = Elem.ParseSingle("cim:PowerTransformer", CIM);
                        if (!XFs.Contains(XF))
                            XFs.Add(XF);
                        if (!XFSubs.Contains(XF.Substation))
                            XFSubs.Add(XF.Substation);
                        MM_KVLevel KVLevel = Data_Manager.LocateKVLevel(Elem.VoltageLevel);
                        if (!XFLevels.Contains(KVLevel))
                            XFLevels.Add(KVLevel);
                    }
                    else if (Elem.TypeName == "cim:SynchronousMachine")
                        Units.Add(Elem);
            LineSubs.Sort(new SubstationComparer());

            //Now, determine if we have a match based on our expectations, and can trust the existing name.                        
            //if (MatchTrace(CIM.PriorModel.Traces, ProcessLog, ContingencySeeds, SeedCount, LineSubs, Units, XFs, out TempName, out Description, Deltas))
              //  ApplyName(ProcessLog, TempName, Description);

            //If we have any lines, let's use that as the basis for the line.
            //else 
            if (LineSubs.Count > 0)
            {
                String KVLevel = Math.Round(float.Parse(HighestVoltage.Name.Split(' ')[0])).ToString();
                String Sub1 = LineSubs[0].Name.Length > 2 ? LineSubs[0].Name.Substring(0, 3).ToUpper() : LineSubs[0].Name.ToUpper();
                String Sub2 = LineSubs[LineSubs.Count - 1].Name.Length > 2 ? LineSubs[LineSubs.Count - 1].Name.Substring(0, 3).ToUpper() : LineSubs[LineSubs.Count - 1].Name.ToUpper();
                TempName = MM_Type_Converter.TitleCase("S" + Sub1 + Sub2 + KVLevel.Substring(KVLevel.Length - 1));
                Description = MM_Type_Converter.TitleCase(LineSubs[0].LongName) + " to " + MM_Type_Converter.TitleCase(LineSubs[LineSubs.Count - 1].LongName) + " " + HighestVoltage.Name;
                ApplyName(ProcessLog, TempName, Description);
                if (LineSubs.Count > 2)
                {
                    ProcessLog.Append(this.Name + ": Multiple lines / > 2 substations detected:");
                    foreach (CIM_Element ElemSub in LineSubs)
                        ProcessLog.Append(" " + ElemSub.Name);
                    ProcessLog.AppendLine();
                }
            }

            //If we have any transformers, build the name that way.
            else if (XFs.Count > 0)
            {
                XFLevels.Sort();
                String Sub1 = XFSubs[0].Name.Length > 2 ? XFSubs[0].Name.Substring(0, 3).ToUpper() : XFSubs[0].Name.ToUpper();
                String KVLevel1 = XFLevels[0].Name.Split(' ')[0];
                String KVLevel2 = XFLevels[XFLevels.Count - 1].Name.Split(' ')[0];
                TempName = "X" + Sub1 + KVLevel1.Substring(KVLevel1.Length - 1) + KVLevel2.Substring(KVLevel2.Length - 1).ToUpper();
                Description = "";
                foreach (MM_KVLevel KVLevel in XFLevels)
                    if (String.IsNullOrEmpty(Description))
                        Description = MM_Type_Converter.TitleCase(XFSubs[0].LongName) + " " + MM_Type_Converter.TitleCase(XFs[0].Name) + " " + KVLevel.Name.Split(' ')[0];
                    else
                        Description += "/" + KVLevel.Name.Split(' ')[0];
                if (XFs.Count == 1)
                    Description += " KV";
                else
                    Description += " KV (" + XFs.Count.ToString() + " transformers)";
                ApplyName(ProcessLog, TempName, Description);
                if (XFs.Count > 1)
                {
                    ProcessLog.Append(this.Name + ": Multiple transformers detected:");
                    foreach (CIM_Element Elem in XFs)
                        ProcessLog.Append(" " + Elem.Name);
                    ProcessLog.AppendLine();
                }
            }

            //If we have any units, update that way
            else if (Units.Count > 0)
            {
                String Sub1 = Units[0].Substation.Name.Length > 2 ? Units[0].Substation.Name.Substring(0, 3).ToUpper() : Substations[0].Name.ToUpper();
                String Unit1 = Units[0].Name.Length > 2 ? Units[0].Name.Substring(0, 3).ToUpper() : Units[0].Name.ToUpper();
                TempName = "U" + Sub1 + Unit1 + Units.Count.ToString();
                Description = MM_Type_Converter.TitleCase(Units[0].Substation.LongName) + " Unit " + MM_Type_Converter.TitleCase(Units[0].Name) + " " + Units[0].VoltageLevel.Name + (Units.Count > 1 ? " (" + Units.Count.ToString() + " units)" : "");
                ApplyName(ProcessLog, TempName, Description);

            }

            //Otherwise, come up with a name based on the substation(s)
            else
            {                
                Description = "Node based contingency: " + VoltageLevels  + " KV in " + ConnectedStationLongNames;
                ApplyName(ProcessLog, Name, Description);
            }
        }

        /// <summary>
        /// Apply our name and add the contingency definition into our database.
        /// </summary>
        /// <param name="ProcessLog"></param>
        /// <param name="ProposedName"></param>
        /// <param name="Description"></param>
        private void ApplyName(StringBuilder ProcessLog, String ProposedName, String Description)
        {
            //First, check our node-based contingencies in a separate way
            if (ProposedName.StartsWith("N_"))
            {

            }
            else if (CIM.Traces.ContainsKey(ProposedName) || CIM.ReservedTraceNames.Contains(ProposedName))
            {
                //Find the character before a number
                int UpdatePosition = ProposedName.Length - 1;
                while (Char.IsNumber(ProposedName[UpdatePosition]))
                    UpdatePosition--;

                //Run through our potential name replacements
                foreach (Char TestChr in "234567890_ABCDEFGHIJKLMNOPQRSTUVWXYZ1".ToCharArray())
                {
                    String NewProposedName = ProposedName.Substring(0, UpdatePosition) + TestChr.ToString() + ProposedName.Substring(UpdatePosition + (ProposedName.Length == 8 ? 1 : 0));
                    if (!CIM.Traces.ContainsKey(NewProposedName) && !CIM.ReservedTraceNames.Contains(NewProposedName))
                    {
                        ProposedName = NewProposedName;
                        break;
                    }
                }
            }
            this.Name = ProposedName;
        }

        /// <summary>
        /// Add an element type to a list if not present
        /// </summary>        
        /// <param name="Collection">Our collection of element types (e.g., voltages, counties, etc.)</param>
        /// <param name="Elem">Our element</param>
        /// <param name="PUNs">Our collection of PUNs</param>
        private void AddIfNotPresent(CIM_Element Elem, List<CIM_Element> Collection, List<CIM_Element> PUNs)
        {
            if (!Collection.Contains(Elem))
                Collection.Add(Elem);
            CIM_Element PUN;
            if (CIM.PUNElements.TryGetValue(Elem.rdfID, out PUN) && !PUNs.Contains(PUN))
                PUNs.Add(PUN);
        }

        /// <summary>
        /// Add a substation to our collection if not present
        /// </summary>
        /// <param name="Elem"></param>
        /// <param name="Collection"></param>
        /// <param name="Counties"></param>
        private void AddSubstationIfNotPresent(CIM_Element Elem, List<CIM_Element> Collection, List<MM_Boundary> Counties)
        {
            if (!Collection.Contains(Elem))
            {
                Collection.Add(Elem);
                MM_Boundary Bound = null;
                MM_Substation FoundSub;
                if (Elem.CIM.Repository.Db.Substations.TryGetValue(Elem.rdfID, out FoundSub))
                    Bound = FoundSub.Boundary;
                if (Bound != null && !Counties.Contains(Bound))
                    Counties.Add(Bound);
            }
        }

        #region Trace grouping information
        
        #endregion

        /// <summary>
        /// Determine which groups the trace should belong to.
        /// </summary>
        /// <param name="ContingencySeeds"></param>
        /// <param name="CtgGroups"></param>
        public void DetermineGroups(Dictionary<CIM_RdfID, CIM_Element> ContingencySeeds, Dictionary<CIM_Element, CIM_Contingency_Grouping> CtgGroups)
        {
            List<CIM_Element> Groups = new List<CIM_Element>();
            foreach (CIM_Contingency_Grouping CtgGroup in CtgGroups.Values)
                if (CtgGroup.TestMembership(this, ContingencySeeds, Groups))                
                    Groups.Add(CtgGroup.BaseElement);
                    
            this.Groups = Groups.ToArray();
        }

    }
}